package org.app.appjson.reader;

import org.app.appjson.exception.AppJsonException;

import java.util.*;

/**
 * Json语法分析器
 * @author lizhongsi
 * @version 1.0
 * @since 1.0
 */
public final class JsonParser {

    /* 词法分析器 */
    private final JsonLexer lexer;

    public JsonParser(JsonLexer lexer) {
        this.lexer = lexer;
    }

//    0 STATUS_EXPECT_SINGLE_VALUE | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_BEGIN_ARRAY
//    1 STATUS_EXPECT_OBJECT_VALUE | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_BEGIN_ARRAY
//    2 STATUS_EXPECT_ARRAY_VALUE | STATUS_EXPECT_BEGIN_ARRAY | STATUS_EXPECT_BEGIN_OBJECT
//    3 STATUS_EXPECT_ARRAY_VALUE | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_BEGIN_ARRAY | STATUS_EXPECT_END_ARRAY
//    4 STATUS_EXPECT_OBJECT_KEY | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_END_OBJECT
//    5 STATUS_EXPECT_COMMA | STATUS_EXPECT_END_OBJECT
//    6 STATUS_EXPECT_COMMA | STATUS_EXPECT_END_ARRAY
//    7 STATUS_EXPECT_OBJECT_KEY
//    8 STATUS_EXPECT_COLON
//    9 STATUS_EXPECT_END_DOCUMENT

    /**
     * 栈实现json读取为Object
     * @return 解析后对象
     * @param <T> 泛型参数
     */
    public <T> T parse() throws AppJsonException {
        Deque<Object> stack = new ArrayDeque<>();
        JsonToken token;
        int status = 0; // 0
        for (;;) {
            token = lexer.next();
            switch (token.getType()) {
                case JsonToken.BOOLEAN:
                    if (status == 0) {
                        stack.push(true);
                        status = 9;
                        continue;
                    } else if (status == 1) {
                        // Map
                        String key = (String) stack.pop();
                        Map<String, Object> map = (Map) stack.peek();
                        if (map == null) {
                            throw new AppJsonException("map is null.");
                        }
                        map.put(key, true);
                        status = 5;
                        continue;
                    }
                    if (status == 2 || status == 3) {
                        // array
                        List<Object> list = (List) stack.peek();
                        if (list == null) {
                            throw new AppJsonException("list is null.");
                        }
                        list.add(true);
                        status = 6;
                        continue;
                    }
                    throw new AppJsonException("Unexpected boolean.");

                case JsonToken.NULL:
                    if (status == 0) {
                        // single null:
                        stack.push(null);
                        status = 9;
                        continue;
                    }
                    if (status == 1) {
                        // Map
                        String key = (String) stack.pop();
                        Map<String, Object> map = (Map) stack.peek();
                        if (map == null) {
                            throw new AppJsonException("map is null.");
                        }
                        map.put(key, null);
                        status = 5;
                        continue;
                    }
                    if (status == 2 || status == 3) {
                        // array
                        List<Object> list = (List) stack.peek();
                        if (list == null) {
                            throw new AppJsonException("list is null.");
                        }
                        list.add(null);
                        status = 6;
                        continue;
                    }
                    throw new AppJsonException("Unexpected null.");

                case JsonToken.NUMBER:
                    if (status == 0) {
                        // single number:
                        stack.push(token.getValue());
                        status = 9;
                        continue;
                    }
                    if (status == 1) {
                        // Map
                        String key = (String) stack.pop();
                        Map<String, Object> map = (Map) stack.peek();
                        if (map == null) {
                            throw new AppJsonException("JsonParser error! map is null.");
                        }
                        map.put(key, token.getValue());
                        status = 5;
                        continue;
                    }
                    if (status == 2 || status == 3) {
                        // array
                        List<Object> list = (List) stack.peek();
                        if (list == null) {
                            throw new AppJsonException("JsonParser error! list is null.");
                        }
                        list.add(token.getValue());
                        status = 6;
                        continue;
                    }
                    throw new AppJsonException("Unexpected number.");

                case JsonToken.STRING:
                    if (status == 0) {
                        // single string:
                        stack.push(token.getValue());
                        status = 9;
                        continue;
                    }
                    if (status == 4 || status == 7) {
                        // push key
                        stack.push(token.getValue());
                        status = 8;
                        continue;
                    }
                    if (status == 1) {
                        // Map
                        String key = (String) stack.pop();
                        Map<String, Object> map = (Map) stack.peek();
                        if (map == null) {
                            throw new AppJsonException("JsonParser error! map is null.");
                        }
                        Object replaced = map.put(key, token.getValue());
                        if (replaced != null) {
                            throw new AppJsonException("duplicate key: " + key);
                        }
                        status = 5;
                        continue;
                    }
                    if (status == 2 || status == 3) {
                        // array
                        List<Object> list = (List) stack.peek();
                        if (list == null) {
                            throw new AppJsonException("JsonParser error! list is null.");
                        }
                        list.add(token.getValue());
                        status = 6;
                        continue;
                    }
                    throw new AppJsonException("JsonParser error! Unexpected a String.");

                case JsonToken.SEP_COLON: // :
                    if (status == 8) {
                        status = 1;
                        continue;
                    }
                    throw new AppJsonException("Unexpected char \\':\\'.");

                case JsonToken.SEP_COMMA: // ,
                    if (status == 5) {
                        status = 7;
                        continue;
                    } else if (status == 6) {
                        status = 2;
                        continue;
                    }
                    throw new AppJsonException("Unexpected char \\',\\'.");

                case JsonToken.END_ARRAY:
                    if (status == 3 || status == 6) {
                        List<Object> list = (List) stack.pop();
                        if (stack.isEmpty()) {
                            stack.push(list);
                            status = 9;
                            continue;
                        }
                        Object object = stack.peek();
                        if (object instanceof String) {
                            // list是一个map的value
                            String key = (String) stack.pop();
                            Map<String, Object> map = (Map) stack.peek();
                            if (map == null) {
                                throw new AppJsonException("map is null.");
                            }
                            Object replaced = map.put(key, list);
                            if (replaced != null) {
                                throw new AppJsonException("duplicate key: " + key);
                            }
                            status = 5;
                            continue;
                        } else if (object instanceof List) {
                            // list是另一个list的元素
                            List<Object> list2 = (List) stack.peek();
                            list2.add(list);
                            status = 6;
                            continue;
                        }
                    }
                    throw new AppJsonException("Unexpected char: \\']\\'.");

                case JsonToken.END_OBJECT:
                    if (status == 4 || status == 5) {
                        Map<String, Object> map = (Map) stack.pop();
                        if (stack.isEmpty()) {
                            stack.push(map);
                            status = 9;
                            continue;
                        }
                        Object object = stack.peek();
                        if (object instanceof String) {
                            // map是map2的value
                            String key = (String) stack.pop();
                            Map<String, Object> map2 = (Map) stack.peek();
                            if (map2 == null) {
                                throw new AppJsonException("map is null.");
                            }
                            map2.put(key, map);
                            status = 5;
                            continue;
                        } else if (object instanceof List) {
                            List<Object> list = (List) stack.peek();
                            list.add(map);
                            status = 6;
                            continue;
                        }
                    }
                    throw new AppJsonException("Unexpected char: \\'}\\'.");

                case JsonToken.END_DOCUMENT:
                    if (status == 9) {
                        Object object = stack.pop();
                        if (stack.isEmpty()) {
                            return (T) object;
                        }
                    }
                    throw new AppJsonException("Unexpected EOF.");

                case JsonToken.BEGIN_ARRAY:
                    if (status == 0 || status == 1 || status == 2 || status == 3) {
                        stack.push(new ArrayList<>());
                        status = 3;
                        continue;
                    }
                    throw new AppJsonException("Unexpected char: \\'[\\'.");

                case JsonToken.BEGIN_OBJECT:
                    if (status == 0 || status == 1 || status == 2 || status == 3 || status == 4) {
                        stack.push(new HashMap<>());
                        status = 4;
                        continue;
                    }
                    throw new AppJsonException("Unexpected char: \\'{\\'.");

                default:
                    throw new AppJsonException("Unexpected switch case.");
            }
        }
    }

    /**
     * 递归读取json字符串返回Object
     * @return 返回对象
     * @param <T> 泛型参数
     */
    public <T> T recursiveParse() throws AppJsonException {
        JsonToken token = lexer.peek();
        switch (token.getType()) {
            case JsonToken.BEGIN_ARRAY:
                List<Object> list = new ArrayList<>();
                lexer.next(); //消费[
                while (lexer.peek().getType() != JsonToken.END_ARRAY) {
                    list.add(recursiveParse());
                    if (lexer.peek().getType() == JsonToken.SEP_COMMA) {
                        lexer.next(); //消费 ,
                    }
                }
                lexer.next(); //消费]
                return (T) list;

            case JsonToken.BEGIN_OBJECT:
                Map<String, Object> map = new HashMap<>();
                lexer.next(); //消费{
                while (lexer.peek().getType() != JsonToken.END_OBJECT) {
                    String key = lexer.next().getValue();
                    lexer.next();// 消费:
                    Object value = recursiveParse();
                    Object replaced = map.put(key, value);
                    if (replaced != null) {
                        throw new AppJsonException("duplicate key: " + key);
                    }
                    if (lexer.peek().getType() == JsonToken.SEP_COMMA) {
                        lexer.next();
                    }
                }
                lexer.next(); //消费}
                return (T) map;

            case JsonToken.STRING:
            case JsonToken.NUMBER:
                token = lexer.next();
                return (T) token.getValue();
            case JsonToken.BOOLEAN:
                lexer.next();
                return (T) Boolean.valueOf(token.getValue());
            case JsonToken.NULL:
                lexer.next();
                return null;
            default:
                throw new IllegalStateException();
        }
    }

}
